package cn.remex.configuration.spring;

import cn.remex.configuration.etcd.EtcdConfigCenter;
import cn.remex.configuration.etcd.client.EtcdV3Client;
import cn.remex.core.exception.ServiceCode;
import cn.remex.core.util.Assert;
import cn.remex.core.util.Judgment;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.*;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.*;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringValueResolver;
import org.springframework.util.SystemPropertyUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Disc:
 * Created by xinxinran on 2017/7/21.
 */
public class RemexEtcdPlaceHolderConfigure extends PlaceholderConfigurerSupport implements EnvironmentAware, BeanNameAware, BeanFactoryAware {

    private Environment environment;

    private MutablePropertySources propertySources;

    private String beanName;

    private BeanFactory beanFactory;

    protected static boolean etcdActived = false;

    private int ETCD_DEFAULT_PORT = 2379;

    private String etcdConnString;

    private String etcdCachedPrefix;

    private String etcdWatchPrefix;

    private EtcdV3Client etcdClient;

    public void setEtcdConnString(String etcdConnString) {
        Map<String, String> etcdConfig = parseEtcdConfig(etcdConnString);
        this.etcdConnString = etcdConfig.get("connString");
        this.etcdCachedPrefix = etcdConfig.get("c");
        this.etcdWatchPrefix = etcdConfig.get("w");
        try{
            EtcdConfigCenter.initEtcdConfig(this.etcdConnString,
                    this.etcdCachedPrefix, this.etcdWatchPrefix, placeholderString -> {
                        List<String> result = new ArrayList<>();
                        if(!Judgment.nullOrBlank(placeholderString)){
                            int nowIndex = 0, beginIndex = 0;
                            boolean beginFlag = false;
                            while(nowIndex > -1){
                                if(beginFlag){
                                    nowIndex = placeholderString.indexOf(SystemPropertyUtils.PLACEHOLDER_SUFFIX, nowIndex);
                                    if(nowIndex > -1){
                                        result.add(placeholderString.substring(beginIndex, nowIndex));
                                    }
                                    beginFlag = false;
                                }else{
                                    nowIndex = placeholderString.indexOf(SystemPropertyUtils.PLACEHOLDER_PREFIX, nowIndex);
                                    if(nowIndex > -1){
                                        beginFlag = true;
                                        beginIndex = nowIndex + SystemPropertyUtils.PLACEHOLDER_PREFIX.length();
                                    }
                                }
                            }
                        }

                        return result;
                    });
        }catch (Exception ignored){}

        etcdClient = EtcdConfigCenter.getClientInstance(etcdConnString);
    }

    private Map<String, String> parseEtcdConfig(String etcdConnString){
        StringBuilder tmpStringBuilder = new StringBuilder();
        Map<String, String> resultMap = new HashMap<>();

        String lastConfig = "connString";
        boolean readKey = false;
        for(char c :etcdConnString.toCharArray()){
            if(c == '?' && "connString".equals(lastConfig)){
                resultMap.put(lastConfig, tmpStringBuilder.toString());
                tmpStringBuilder = new StringBuilder();
                readKey = true;
            }else if(readKey && c == '='){
                readKey = false;
                lastConfig = tmpStringBuilder.toString();
                tmpStringBuilder = new StringBuilder();
            }else if(!readKey && c == '&'){
                readKey = true;
                resultMap.put(lastConfig, tmpStringBuilder.toString());
                tmpStringBuilder = new StringBuilder();
            }else{
                tmpStringBuilder.append(c);
            }
        }

        if(!readKey){
            resultMap.put(lastConfig, tmpStringBuilder.toString());
        }

        return resultMap;
    }

    @Override
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    public void setPropertySources(PropertySources propertySources) {
        this.propertySources = new MutablePropertySources(propertySources);
    }

    @Override
    protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) {
        BeanDefinitionEtcdWatchVisitor visitor = new BeanDefinitionEtcdWatchVisitor(beanFactory, valueResolver, this.etcdWatchPrefix);
        String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();

        for (String curName : beanNames) {
            if (!curName.equals(this.beanName) || !beanFactoryToProcess.equals(this.beanFactory)) {
                BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);

                try {
                    visitor.visitBeanDefinition(bd);
                } catch (Exception e) {
                    throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, e.getMessage());
                }
            }
        }

        beanFactoryToProcess.resolveAliases(valueResolver);
        beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
    }

    @Deprecated
    @Override
    protected void processProperties(ConfigurableListableBeanFactory configurableListableBeanFactory, Properties properties) throws BeansException {
    }

    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, final ConfigurablePropertyResolver propertyResolver) throws BeansException {
        propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
        propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
        propertyResolver.setValueSeparator(this.valueSeparator);
        StringValueResolver valueResolver = new StringValueResolver() {
            private ExpressionParser expressionParser = new SpelExpressionParser();

            private final Map<String, Expression> expressionCache = new ConcurrentHashMap<String, Expression>(256);

            private final String etcdExpressPrefix = "#{";

            private final String etcdExpressSuffix = "}";

            private final Map<BeanExpressionContext, StandardEvaluationContext> evaluationCache = new ConcurrentHashMap<>();

            private final ParserContext beanExpressionParserContext = new ParserContext() {
                public boolean isTemplate() {
                    return true;
                }

                public String getExpressionPrefix() {
                    return etcdExpressPrefix;
                }

                public String getExpressionSuffix() {
                    return etcdExpressSuffix;
                }
            };

            public String resolveStringValue(String strVal) {
                String resolved = RemexEtcdPlaceHolderConfigure.this.ignoreUnresolvablePlaceholders ? propertyResolver.resolvePlaceholders(strVal) : propertyResolver.resolveRequiredPlaceholders(strVal);

                return resolved.equals(RemexEtcdPlaceHolderConfigure.this.nullValue) ? null : resolved;
            }
        };
        this.doProcessProperties(beanFactoryToProcess, valueResolver);
    }


    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (this.propertySources == null) {
            this.propertySources = new MutablePropertySources();
            if (this.environment != null) {
                this.propertySources.addLast(new PropertySource<Environment>("environmentProperties", this.environment) {
                    public String getProperty(String key) {
                        return this.source.getProperty(key);
                    }
                });
            }

            try {
                PropertiesPropertySource ex = new PropertiesPropertySource("localProperties", processEtcdProperties(this.mergeProperties()));
                if (this.localOverride) {
                    this.propertySources.addFirst(ex);
                } else {
                    this.propertySources.addLast(ex);
                }
            } catch (IOException e) {
                throw new BeanInitializationException("Could not load properties", e);
            }
        }

        if(RemexEtcdPlaceHolderConfigure.etcdActived){
            this.processProperties(beanFactory, new PropertySourcesPropertyEtcdResolver(this.propertySources, this.etcdConnString, this.placeholderPrefix + "%s" + this.placeholderSuffix));
        }else{
            this.processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
        }
    }


    private Properties processEtcdProperties(Properties properties) throws BeansException {
        String etcdSign = properties.getProperty("enableEtcd", "false");

        if (etcdClient != null && ("true".equalsIgnoreCase(etcdSign) || "1".equalsIgnoreCase(etcdSign) || "yes".equalsIgnoreCase(etcdSign))) {
            try {
                Assert.notNullAndEmpty(this.etcdConnString, ServiceCode.ERROR, "etcd connection string required ");

                RemexEtcdPlaceHolderConfigure.etcdActived = true;
//                for (Map.Entry<Object, Object> pEntry : properties.entrySet()) {
//                    String pValue = pEntry.getValue().toString();
//
//                    if (pValue.startsWith("etcd://")) {
//                        pEntry.setValue(etcdClient.getValue(pValue.substring(6)).get());
//                    }
//                }

                EtcdConfigCenter.setMergedProperties(properties);
            } catch (Throwable e) {
                throw new BeanInitializationException("error occurred while replacing with etcd, because : " + e.getMessage());
            }
        }
        properties.setProperty("SYSTEM_CONTEXT_CLASS_PATH", RemexEtcdPlaceHolderConfigure.class.getResource("/").getPath());
        return properties;
    }
}
